#include <event2/event.h>
#include <stdio.h>
#include <errno.h>
#include "zip_dns_server.h"
#include "zip_socket.h"
#include "zip_utils.h"

#define TCP_MAX_PACKAGE_LEN 4096

typedef struct dns_udp_server
{
    addr_t server_address_;
    socket_t server_socket_;
    dns_package_handler_t handler_;
    void *user_data_;

    struct event_base *base_event_;
    struct event *udp_read_event_;

}dns_udp_server_t;

struct dns_server
{
    server_t server_type_;
    dns_udp_server_t *udp_server_;
};

static dns_udp_server_t*
dns_udp_server_create(struct event_base *base, const char *ip, uint16_t port);

static void
dns_udp_server_delete(dns_udp_server_t *udp_server);

static void
dns_udp_server_start(dns_udp_server_t *server,
                     dns_package_handler_t handler,
                     void *user_data);

static void
dns_udp_server_stop(dns_udp_server_t *server);

static void
dns_udp_server_recv_data_call_back(evutil_socket_t fd, short event, void *arg);

dns_server_t *
dns_server_create(struct event_base *base,
                  server_t type,
                  const char *ip,
                  uint16_t port)
{
    dns_server_t *server = (dns_server_t *)calloc(1, sizeof(dns_server_t));
    if (server)
    {
        server->server_type_ = type;
        if (type == UDP_SERVER)
        {
            server->udp_server_ = dns_udp_server_create(base, ip, port);
        }
    }

    return server;
}

static dns_udp_server_t *
dns_udp_server_create(struct event_base *base, const char *ip, uint16_t port)
{
    dns_udp_server_t *server = malloc(sizeof(dns_udp_server_t));
    if (server)
    {
        addr_init(&server->server_address_, ip, port);
        server->base_event_ = base;
    }

    return server;
}

void
dns_server_delete(dns_server_t *server)
{
    if (server->server_type_ == UDP_SERVER)
    {
        dns_udp_server_delete(server->udp_server_);
    }
    free(server);
}

static void
dns_udp_server_delete(dns_udp_server_t *server)
{
    event_free(server->udp_read_event_);
    socket_close(&server->server_socket_);
    free(server);
}

void
dns_server_start(dns_server_t *server,
                 dns_package_handler_t handler,
                 void *user_data)
{
    if (server->server_type_ == UDP_SERVER)
        dns_udp_server_start(server->udp_server_, handler, user_data);
}

static void
dns_udp_server_start(dns_udp_server_t *server,
                     dns_package_handler_t handler,
                     void *user_data)
{
    server->handler_ = handler;
    server->user_data_ = user_data;

    const addr_t *addr = &server->server_address_;
    socket_t *listen_socket = &server->server_socket_;
    int socket_type = addr_get_type(addr) == ADDR_IPV4 ? AF_INET : AF_INET6;
    socket_open(listen_socket, socket_type, SOCK_DGRAM, 0);
    socket_set_unblock(listen_socket, true);
    socket_set_addr_reusable(listen_socket);

    int ret = socket_bind(listen_socket, addr);
    if (ret != 0)
    {
        perror("udp server bind failed\n");
        goto START_FAILED;
    }

    server->udp_read_event_ = event_new(server->base_event_,
                                        SOCKET_FD(listen_socket),
                                        EV_READ|EV_PERSIST,
                                        dns_udp_server_recv_data_call_back,
                                        (void*)server);
    if (server->udp_read_event_ == NULL)
    {
        perror("udp read event create failed\n");
        goto START_FAILED;
    }

    event_add(server->udp_read_event_, NULL);
    return;

START_FAILED:
    socket_close(listen_socket);
}

static void
dns_udp_server_recv_data_call_back(evutil_socket_t fd, short event, void *arg)
{
    dns_udp_server_t *server = (dns_udp_server_t*)arg;
    socket_t *listen_socket = &server->server_socket_;
    uint8_t buffer[UDP_MAX_PACKAGE_LEN];
    addr_t client_addr;
    uint16_t len = socket_read_from(listen_socket,
                                    buffer,
                                    UDP_MAX_PACKAGE_LEN,
                                    &client_addr);
    (server->handler_)(buffer,
                       len,
                       listen_socket,
                       &client_addr,
                       server->user_data_);
}

void
dns_server_stop(dns_server_t *server)
{
    if (server->server_type_ == UDP_SERVER)
    {
        dns_udp_server_stop(server->udp_server_);
    }
}

static void
dns_udp_server_stop(dns_udp_server_t *server)
{
    if (server->udp_read_event_)
        event_del(server->udp_read_event_);
}
